package mf.words.grammar;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import mf.tools.Tuple;

/**
 * Creates a straight-line-grammar out of a specified file.
 */
public class rePair {
	
	//help text displayed when you dont specify a file to rePair
	private final static String helpText = 
		"Usage: rePair <file> [output] [debug]\n"+
	    "\t<file>: Path to the file we want to rePair.\n" +
	    "\t[output]: Path of the output file. Optional.\n"+
	    "\t[debug]: Show debugging informations. Optional.\n";
	
	//debug mode displays more informations
	private static boolean DEBUG_MODE = false;
	
	/** Repair file. */
	public static void main(String[] args) throws IOException {
		String output = null;
		//parameter evaluation{
		switch (args.length) {
		case 0:
			System.out.println(helpText);
			System.exit(1);
			break;
		case 1:
			output = args[0];
			break;
		case 2:
			output = args[1];
			break;
		default:
			output = args[1];
			
			if(args[2].equals("debug"))
				DEBUG_MODE = true;
			break;
		}

		//start the program
		System.out.println("rePair\nLoading file "+args[0]);
		
		//create file reader object and quit if file doesn't exist
		FileInputStream fr = new FileInputStream(args[0]);

		//variables for text reading
		int last = fr.read(), cur = last;
		ArrayList<Integer> text = new ArrayList<Integer>();
		
		//variables for digramms
		HashMap<String, Integer> digramms = new HashMap<String, Integer>();
		Tuple<Integer> mfdi = new Tuple<Integer>();
		int cdi = 0;
		
		//variables for the rules
		int var_num = 256;
		ArrayList<Tuple<Integer>> rules = new ArrayList<Tuple<Integer>>();
		
		//read file and count digramms
		text.add(last);
		while(true) {
			//read next symbol and append to text
			cur = fr.read();
			if(cur == -1)
				break;
			text.add(cur);
			
			//increment count of digramm (last, cur)
			String curdi = last + "," + cur;
			int old_count = (digramms.get(curdi) == null) ? 0 : digramms.get(curdi);
			digramms.put(curdi, old_count+1);
			
			//get most frequent digramm
			if(old_count+1 > cdi) {
				cdi = old_count + 1;
				mfdi.set(last, cur);
			}
			
			last = cur;
		}
		
		if(DEBUG_MODE) 
			System.out.println(
				"file size: "+text.size()+" Bytes\n"+
				"digramms="+digramms.toString()+"\n"+
				"most frequent digramm is "+mfdi.toString()+" with count "+ cdi+"\n"
			);
		
		//as long as there are digramms with more than one occurence, replace them with a new variable
		int i, new_cdi;
		Tuple<Integer> new_mfdi;
		
		while (cdi > 1) {
		    i = 0;
		    digramms.clear();
		    new_cdi = 0;
		    new_mfdi = new Tuple<Integer>();
		    
		    //replace most frequent digramm and recount
		    while (i < text.size()) {
		        //check for replace iff not at the end of text
		    	if(i+1 < text.size())
		    		if (text.get(i).equals(mfdi.left()) && text.get(i + 1).equals(mfdi.right())) {
		    			text.set(i, var_num);
		    			text.remove(i+1);
		    		}
		        
		        //count next digramm if not at the beginning of text
		        if(i > 0) {
		        	String curdi = text.get(i-1) + "," + text.get(i);
		        	int old_count = (digramms.get(curdi) == null) ? 0 : digramms.get(curdi);
		        	digramms.put(curdi, old_count+1);
		        	
		        	//get most frequent digramm
		        	if(old_count +1 > new_cdi) {
		        		new_cdi = old_count+1;
		        		new_mfdi.set(text.get(i-1), text.get(i));
		        	}
		        }
		        
		        //next round
		        i++;
		    }
		    
		    //add rule, increase variable number and set new mfdi
		    rules.add(mfdi);
		    var_num++;
		    mfdi=new_mfdi;
		    cdi=new_cdi;
		    
		    //show results
		    if(DEBUG_MODE) 
		    	System.out.println(
		    		"digramms="+digramms.toString()+"\n"+
		    		"most frequent digramm is "+mfdi.toString()+" with count "+cdi+"\n"
		    	);
		}
		
		//save rules to file
		FileOutputStream fw = new FileOutputStream(output+".jPair");

		//write a 4 Byte ascii representation of rules.size() to the file
		int[] rep = toBase256(rules.size(), 4);
		
		for(byte k=0; k<4; k++)
			fw.write(rep[k]);
		
		//write rules to file
		if(DEBUG_MODE)
			System.out.println("Grammar has " + rules.size() + " rules.");
		
		int blockLen = 1 + (int)(Math.log10(256 + rules.size()) / Math.log10(256));
		for(i=0; i<rules.size(); i++){
			int l = rules.get(i).left(), r = rules.get(i).right();
			
			int[] l_256 = toBase256(l, blockLen),
				  r_256 = toBase256(r, blockLen);

			for(byte k=0; k<blockLen; k++)
				fw.write(l_256[k]);
			for(byte k=0; k<blockLen; k++)
				fw.write(r_256[k]);
			
			if(DEBUG_MODE)
				System.out.println(
					"v_{"+(i+256)+"} -> "+
					(l<256 ? (char)l : "v_{"+l+"}")+
					(r<256 ? (char)r : "v_{"+r+"}")
				);
		}
		
		//write a blockLen-Byte-sized ascii-representation of the remaining string to the file
		if(DEBUG_MODE)
			System.out.println("\nremaining text: "+text+"\nsize: "+text.size());
		
		for(i=0; i<text.size(); i++) {
			int[] t_256 = toBase256(text.get(i), blockLen);
			
			for(byte k=0; k<blockLen; k++)
				fw.write(t_256[k]);
		}
			
		fw.close();

		System.out.println(
			"\n" +
			"rePairing done\n" +
			"Saved file to " + args[0]+".jPair\n" +
			"grammar size:" +
			(rules.size() * blockLen * 2 + text.size() * blockLen + 4)+
			" Bytes"
		);
	}
	
	/** Converts the (variable) number <b>nr</b> to an ascii-string padded to length <b>len</b>. */
	private static int[] toBase256(int nr, int len) {
	    int i = (int)(Math.log10(nr)/Math.log10(256));
	    int[] ret = new int[len];
	    
	    while(i > 0) {
	    	ret[len-i-1] = nr / (int)Math.pow(256, i);
	    	nr = nr % (int)Math.pow(256, i);
	    	i -= 1;
	    }
	    
	    ret[len-1] = nr;
	    
		return ret;
	}
}